﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Collections.Generic;

namespace Kyoh.Lib.WinForms
{
    /// <summary>行を移動できるDataGridViewです。</summary>
    [ClassInterface(ClassInterfaceType.AutoDispatch)]
    [Docking(DockingBehavior.Ask)]
    [DefaultEvent("CellContentClick")]
    [ComplexBindingProperties("DataSource", "DataMember")]
    public class DataGridViewPlus : DataGridView, ISupportInitialize
    {
        /// <summary><see cref="DataGridViewPlus"/> クラスの新しいインスタンスを初期化します。</summary>
        public DataGridViewPlus()
        {
        }

        /// <summary>選択中の行を一行下に移動します。</summary>
        /// <param name="cyclic">一番下の行から一番上の行へ循環する場合は <c>true</c> を指定します。</param>
        public void MoveNext(bool cyclic)
        {
            this.Shift(1, cyclic);
        }

        /// <summary>選択中の行を一行上に移動します。</summary>
        /// <param name="cyclic">一番上の行から一番下の行へ循環する場合は <c>true</c> を指定します。</param>
        public void MovePrev(bool cyclic)
        {
            this.Shift(-1, cyclic);
        }

        private void Shift(int shift, bool cyclic)
        {
            var count = this.Rows.Count;
            var sels = this.SelectedRows;

            if (sels.Count > 0)
            {
                this.ClearSelection();

                int index;
                if (cyclic)
                    index = (sels[0].Index + count + shift)%count;
                else
                    index = Math.Min(Math.Max(sels[0].Index + shift, 0), count - 1);
                this.Rows[index].Selected = true;
            }
        }

        private bool drawRowNumber;

        /// <summary>行ヘッダに行番号を表示するかどうかを取得・設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(false)]
        public bool DrawRowNumber
        {
            get { return this.drawRowNumber; }
            set
            {
                if (this.drawRowNumber == value)
                    return;
                this.drawRowNumber = value;

                this.Update();
            }
        }

        /// <summary>行番号を描画します。</summary>
        /// <param name="e">イベントデータを格納する <see cref="DataGridViewRowPostPaintEventArgs"/>。</param>
        protected override void OnRowPostPaint(DataGridViewRowPostPaintEventArgs e)
        {
            if (DrawRowNumber && RowHeadersVisible)
            {
                //行番号を描画する範囲を決定する
                var rect = new Rectangle(
                    e.RowBounds.Left, e.RowBounds.Top,
                    RowHeadersWidth, e.RowBounds.Height);
                rect.Inflate(-2, -2);
                //行番号を描画する
                TextRenderer.DrawText(e.Graphics,
                    (e.RowIndex + 1).ToString(CultureInfo.CurrentUICulture),
                    e.InheritedRowStyle.Font,
                    rect,
                    e.InheritedRowStyle.ForeColor,
                    TextFormatFlags.Right | TextFormatFlags.VerticalCenter);
            }
            base.OnRowPostPaint(e);
        }

        protected override void OnDataBindingComplete(DataGridViewBindingCompleteEventArgs e)
        {
            base.OnDataBindingComplete(e);
        }
        private delegate bool Selector(bool prev);
        private bool SelXor(bool prev) { return prev ^ true; }
        private bool SelOr(bool prev) { return true; }
        private Point? controledCell;

        /// <summary>セルのクリックに応じてセルの選択/選択解除を行います。</summary>
        protected override void OnCellMouseDown(DataGridViewCellMouseEventArgs e)
        {
            Point clickedCell = new Point(e.ColumnIndex, e.RowIndex);
            Point beginCell;
            Selector selector;
            Keys modifier = MultiSelect ? (ModifierKeys & (Keys.Control | Keys.Shift)) : 0;
            switch (modifier)
            {
                case Keys.Control:
                    beginCell = clickedCell;
                    selector = SelXor;
                    break;
                case Keys.Control | Keys.Shift:
                    beginCell = controledCell ?? CurrentCellAddress;
                    selector = SelOr;
                    break;
                case Keys.Shift:
                    beginCell = CurrentCellAddress;
                    selector = SelOr;
                    ClearSelection();
                    break;
                default:
                    if (e.Button == MouseButtons.Right && (e.ColumnIndex < 0 || e.RowIndex < 0 || this[e.ColumnIndex, e.RowIndex].Selected))
                    {
                        base.OnCellMouseDown(e);
                        return;
                    }
                    beginCell = clickedCell;
                    selector = SelOr;
                    ClearSelection();
                    break;
            }

            controledCell = clickedCell;

            Point minCell = new Point(Math.Min(beginCell.X, clickedCell.X), Math.Min(beginCell.Y, clickedCell.Y));
            Point maxCell = new Point(Math.Max(beginCell.X, clickedCell.X), Math.Max(beginCell.Y, clickedCell.Y));

            switch (SelectionMode)
            {
                case DataGridViewSelectionMode.CellSelect:
                    foreach (int column in Enumerate(minCell.X, maxCell.X, ColumnCount))
                        foreach (int row in Enumerate(minCell.Y, maxCell.Y, RowCount))
                            this[row, column].Selected = selector(this[row, column].Selected);
                    break;
                case DataGridViewSelectionMode.FullColumnSelect:
                    if (minCell.X >= 0)
                        foreach (int column in Enumerate(minCell.X, maxCell.X, ColumnCount))
                            this.Columns[column].Selected = selector(this.Columns[column].Selected);
                    break;
                case DataGridViewSelectionMode.FullRowSelect:
                    if (minCell.Y >= 0)
                        foreach (int row in Enumerate(minCell.Y, maxCell.Y, RowCount))
                            this.Rows[row].Selected = selector(this.Rows[row].Selected);
                    break;
                case DataGridViewSelectionMode.ColumnHeaderSelect:
                    if (maxCell.Y == -1 && minCell.Y == -1)
                        foreach (int column in Enumerate(minCell.X, maxCell.X, ColumnCount))
                            this.Columns[column].Selected = selector(this.Columns[column].Selected);
                    break;
                case DataGridViewSelectionMode.RowHeaderSelect:
                    if (maxCell.X == -1 && minCell.X == -1)
                        foreach (int row in Enumerate(minCell.Y, maxCell.Y, RowCount))
                            this.Rows[row].Selected = selector(this.Rows[row].Selected);
                    break;
            }

            switch (modifier)
            {
                case Keys.Control:
                    break;
                case Keys.Control | Keys.Shift:
                    break;
                case Keys.Shift:
                    break;
                default:
                    base.OnCellMouseDown(e);
                    break;
            }
        }

        private IEnumerable<int> Enumerate(int min, int max, int count)
        {
            if (max < 0)
                yield break;
            for (int i = Math.Min(Math.Max(0, min), count); i <= Math.Min(max, count); i++)
                yield return i;
        }
    }
}